/*****************************************************************************
 *
 *  $Id: CommandSlaves.cpp,v bc2d4bf9cbe5 2012/09/06 18:22:24 fp $
 *
 *  Copyright (C) 2006-2009  Florian Pose, Ingenieurgemeinschaft IgH
 *
 *  This file is part of the IgH EtherCAT Master.
 *
 *  The IgH EtherCAT Master is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License version 2, as
 *  published by the Free Software Foundation.
 *
 *  The IgH EtherCAT Master is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
 *  Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with the IgH EtherCAT Master; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 *  ---
 *
 *  The license mentioned above concerns the source code only. Using the
 *  EtherCAT technology and brand is only permitted in compliance with the
 *  industrial property and similar rights of Beckhoff Automation GmbH.
 *
 ****************************************************************************/

#include <iostream>
#include <iomanip>
#include <list>
#include <string.h>
#include <unistd.h>
#include <fstream>
using namespace std;

#include "CommandCode.h"
#include "MasterDevice.h"

/*****************************************************************************/

CommandCode::CommandCode():
    Command("code", "Display slaves product code.")
{
}

/*****************************************************************************/

string CommandCode::helpString(const string &binaryBaseName) const
{
    stringstream str;

    str << binaryBaseName << " " << getName() << " [OPTIONS]" << endl
        << endl
        << getBriefDescription() << endl
        << endl
        << "If the --verbose option is not given, the slaves are" << endl
        << "displayed one-per-line. Example:" << endl
        << endl
        << "1  5555:0  PREOP  +  EL3162 2C. Ana. Input 0-10V" << endl
        << "|  |    |  |      |  |" << endl
        << "|  |    |  |      |  \\- Name from the SII if avaliable," << endl
        << "|  |    |  |      |     otherwise vendor ID and product" << endl
        << "|  |    |  |      |     code (both hexadecimal)." << endl
        << "|  |    |  |      \\- Error flag. '+' means no error," << endl
        << "|  |    |  |         'E' means that scan or" << endl
        << "|  |    |  |         configuration failed." << endl
        << "|  |    |  \\- Current application-layer state." << endl
        << "|  |    \\- Decimal relative position to the last" << endl
        << "|  |       slave with an alias address set." << endl
        << "|  \\- Decimal alias address of this slave (if set)," << endl
        << "|     otherwise of the last slave with an alias set," << endl
        << "|     or zero, if no alias was encountered up to this" << endl
        << "|     position." << endl
        << "\\- Absolute ring position in the bus." << endl
        << endl
        << "If the --verbose option is given, a detailed (multi-line)" << endl
        << "description is output for each slave." << endl
        << endl
        << "Slave selection:" << endl
        << "  Slaves for this and other commands can be selected with" << endl
        << "  the --alias and --position parameters as follows:" << endl
        << endl
        << "  1) If neither the --alias nor the --position option" << endl
        << "     is given, all slaves are selected." << endl
        << "  2) If only the --position option is given, it is" << endl
        << "     interpreted as an absolute ring position and" << endl
        << "     a slave with this position is matched." << endl
        << "  3) If only the --alias option is given, all slaves" << endl
        << "     with the given alias address and subsequent" << endl
        << "     slaves before a slave with a different alias" << endl
        << "     address match (use -p0 if only the slaves" << endl
        << "     with the given alias are desired, see 4))." << endl
        << "  4) If both the --alias and the --position option are" << endl
        << "     given, the latter is interpreted as relative" << endl
        << "     position behind any slave with the given alias." << endl
        << endl
        << "Command-specific options:" << endl
        << "  --alias    -a <alias>  Slave alias (see above)." << endl
        << "  --position -p <pos>    Slave position (see above)." << endl
        << "  --verbose  -v          Show detailed slave information." << endl
        << endl
        << numericInfo();

    return str.str();
}

string CommandCode::resultText(int result)
{
    switch (result) {
        case FOE_BUSY:
            return "FOE_BUSY";
        case FOE_READY:
            return "FOE_READY";
        case FOE_IDLE:
            return "FOE_IDLE";
        case FOE_WC_ERROR:
            return "FOE_WC_ERROR";
        case FOE_RECEIVE_ERROR:
            return "FOE_RECEIVE_ERROR";
        case FOE_PROT_ERROR:
            return "FOE_PROT_ERROR";
        case FOE_NODATA_ERROR:
            return "FOE_NODATA_ERROR";
        case FOE_PACKETNO_ERROR:
            return "FOE_PACKETNO_ERROR";
        case FOE_OPCODE_ERROR:
            return "FOE_OPCODE_ERROR";
        case FOE_TIMEOUT_ERROR:
            return "FOE_TIMEOUT_ERROR";
        case FOE_SEND_RX_DATA_ERROR:
            return "FOE_SEND_RX_DATA_ERROR";
        case FOE_RX_DATA_ACK_ERROR:
            return "FOE_RX_DATA_ACK_ERROR";
        case FOE_ACK_ERROR:
            return "FOE_ACK_ERROR";
        case FOE_MBOX_FETCH_ERROR:
            return "FOE_MBOX_FETCH_ERROR";
        case FOE_READ_NODATA_ERROR:
            return "FOE_READ_NODATA_ERROR";
        case FOE_MBOX_PROT_ERROR:
            return "FOE_MBOX_PROT_ERROR";
        default:
            return "???";
    }
}






string CommandCode::errorText(int errorCode)
{
    switch (errorCode) {
        case 0x00008001:
            return "Not found.";
        case 0x00008002:
            return "Access denied.";
        case 0x00008003:
            return "Disk full.";
        case 0x00008004:
            return "Illegal.";
        case 0x00008005:
            return "Packet number wrong.";
        case 0x00008006:
            return "Already exists.";
        case 0x00008007:
            return "No user.";
        case 0x00008008:
            return "Bootstrap only.";
        case 0x00008009:
            return "Not Bootstrap.";
        case 0x0000800a:
            return "No rights.";
        case 0x0000800b:
            return "Program Error.";
        default:
            return "Unknown error code";
    }
}

unsigned int CommandCode::getVersion(MasterDevice &m, SlaveList &slaves,string sourceName)
{
    ec_ioctl_slave_t *slave;
    ec_ioctl_slave_foe_t data;
    unsigned int i;
    stringstream err;

    if (slaves.size() != 1) {
        throwSingleSlaveRequired(slaves.size());
    }

    slave = &slaves.front();
    data.slave_position = slave->position;

    /* FIXME: No good idea to have a fixed buffer size.
     * Read in chunks and fill a buffer instead.
     */
    data.offset = 0;
    data.buffer_size = 0x8800;
    data.buffer = new uint8_t[data.buffer_size];

    strncpy(data.file_name, sourceName.c_str(), sizeof(data.file_name));

    try {
        m.readFoe(&data);
    } catch (MasterDeviceException &e) {
        delete [] data.buffer;
        // if (data.result) {
        //     if (data.result == FOE_OPCODE_ERROR) {
        //         err << "FoE read aborted with error code 0x"
        //             << setw(8) << setfill('0') << hex << data.error_code
        //             << ": " << errorText(data.error_code);
        //     } else {
        //         err << "Failed to write via FoE: "
        //             << resultText(data.result);
        //     }
        //     throwCommandException(err);
        // } else {
        //     throw e;
        // }
        return 0;
    }

    // TODO --output-file

    uint32_t *w =(uint32_t *) data.buffer;
    // cout <<"bootVersion:    0x"<<setfill('0')<<setw(8)<< hex <<*w<<endl;
    #if 0
    for (i = 0; i < data.data_size; i++) {
        uint8_t *w = data.buffer + i;
        cout <<setfill('0')<<setw(2)<< hex <<(uint32_t)*(uint8_t *) w ;
    }
    cout<<endl;
    #endif

    delete [] data.buffer;

    return *w;
}

void CommandCode::showVersion(string name,unsigned int version)
{   
    if(name == "appversion")
    {
        cout <<name<<":    0x"<<setfill('0')<<setw(8)<< dec <<version<<endl;  
        return ;  
    }
    cout <<name<<":    0x"<<setfill('0')<<setw(8)<< hex <<version<<endl;
}

/****************************************************************************/

void CommandCode::execute(const StringVector &args)
{
	MasterIndexList masterIndices;
    SlaveList slaves;
    ec_ioctl_slave_foe_t data;
    unsigned int bootVersion = 0;
    unsigned int appVersion = 0;
    // if (args.size()) {
    //     stringstream err;
    //     err << "'" << getName() << "' takes no arguments!";
    //     throwInvalidUsageException(err);
    // }


	masterIndices = getMasterIndices();
    
    MasterIndexList::const_iterator mi;
    for (mi = masterIndices.begin();
            mi != masterIndices.end(); mi++) {
        MasterDevice m(*mi);
        // 以读写的方式
        m.open(MasterDevice::ReadWrite);
        slaves = selectedSlaves(m);
        
        getVersion_byname(m,slaves,"BOOTVERSION",&bootVersion);
        getVersion_byname(m,slaves,"VERSION",&appVersion);
        
        showProdoctCode(m, slaves);
        showVersion("bootversion",bootVersion);
        showVersion("appversion",appVersion);

        // 假设有传递进来固件的，则实现升级功能，否则只是显示bootversion，appversion，productcode
        if (args.size()) {
            getFoeData(args[0],&data);
            updateFirmware(&data,m,slaves);
            getVersion_byname(m,slaves,"VERSION",&appVersion);
            showVersion("appversion",appVersion);
            setStates(m,"INIT",slaves);
            setStates(m,"PREOP",slaves);
        }
        
       
    }
}

unsigned int CommandCode::getVersion_byname(MasterDevice &m, SlaveList &slaves,string sourcename,unsigned int *value)
{
    int getCount = 10;
    *value = 0;

    while(getCount--)
    {
        this->setStates(m,"BOOT",slaves);
        sleep(3);
        *value = getVersion(m,slaves,sourcename);
        if(*value == 0)
        {
            continue;
        }
        else
        {
            break;
        }
    }

    if(*value == 0)
    {
        cout<<"get "<<sourcename<<" version error ,maybe no supported.."<<endl;  
    }

    return *value;

}
 
/****************************************************************************/
// 切换slave的状态
void CommandCode::setStates(MasterDevice &m,string stateStr,const SlaveList &slaves)
{
    SlaveList::const_iterator si;
    stringstream err;
   
    uint8_t state = 0x00;


    if (stateStr == "INIT") {
        state = 0x01;
    } else if (stateStr == "PREOP") {
        state = 0x02;
    } else if (stateStr == "BOOT") {
        state = 0x03;
    } else if (stateStr == "SAFEOP") {
        state = 0x04;
    } else if (stateStr == "OP") {
        state = 0x08;
    } else {
        err << "Invalid state";
        throwInvalidUsageException(err);
    }


    for (si = slaves.begin(); si != slaves.end(); si++) {
        m.requestState(si->position, state);
    }
}
/****************************************************************************/
// 读取和解析固件信息
int CommandCode::getFoeData(string filepath,ec_ioctl_slave_foe_t *data)
{
     ifstream file;

     file.open(filepath.c_str(), ifstream::in | ifstream::binary);
    if (file.fail()) {
        cout<<"fail open file"<<endl;
        
        return -1;
    }
    loadFoeData(data, file);
    file.close();

    return 0;
}
// 升级
int CommandCode::update(ec_ioctl_slave_foe_t *data,MasterDevice &m, SlaveList &slaves)
{
    ec_ioctl_slave_t *slave;
    // ec_ioctl_slave_foe_t data;
    // ifstream file;
    string storeFileName;
    stringstream err;
    string outputfileName = "APP";

    // file.open(filepath.c_str(), ifstream::in | ifstream::binary);
    // if (file.fail()) {
    //     cout<<"fail open file"<<endl;
    // }
    // loadFoeData(&data, file);
    // file.close();

    // char *cpy = strdup(filepath.c_str()); // basename can modify  the string contents
    // storeFileName = basename(cpy);
    // free(cpy);

    slave = &slaves.front();
    data->slave_position = slave->position;
    data->offset = 0;
    strncpy(data->file_name, outputfileName.c_str(), sizeof(data->file_name));

    try {
        m.writeFoe(data);
    } catch (MasterDeviceException &e) {
        // if (data->buffer_size)
        // {
        //     delete [] data->buffer;
        // }
       
        // if (data.result) {
        //     if (data.result == FOE_OPCODE_ERROR) {
        //         err << "FoE write aborted with error code 0x"
        //             << setw(8) << setfill('0') << hex << data.error_code
        //             << ": " << errorText(data.error_code);
        //     } else {
        //         err << "Failed to write via FoE: "
        //             << resultText(data.result);
        //     }
        //     throwCommandException(err);
        // } else {
        //     throw e;
        // }
        // 发生异常时，将返回，方便外层使用返回值判断是否成功
         return -1;
    }

    if (data->buffer_size)
        delete [] data->buffer;
    
    return 0;
}

int CommandCode::updateFirmware(ec_ioctl_slave_foe_t *data,MasterDevice &m, SlaveList &slaves)
{

    int updateTryCnt = 10;
    int ret = 0;

    while(updateTryCnt--)
    {
        setStates(m,"BOOT",slaves);
        sleep(3);
        ret = update(data,m,slaves);
        cout<<"update ret = "<<ret<<endl;
        if(ret < 0)
        {
            continue;
        }
        else
        {
            break;
        }
    }
    if(ret < 0)
    {
        cout<<"update error"<<endl;
    }
    cout<<"updateTryCnt="<<updateTryCnt<<endl;
    return ret;
}
void CommandCode::loadFoeData(
        ec_ioctl_slave_foe_t *data,
        const istream &in
        )
{
    stringstream err;
    ostringstream tmp;
    int loglen=0; 
     
    int i = 0,j=0;
    int len = 0;
    string buf;
    tmp << in.rdbuf();
    
    string const &contents = tmp.str();
    cout<<"file size="<<contents.size()<<endl;
    for(i = 0; i < 8;i++)
    {
        buf.push_back(contents[i*2]);
    }
    len =i*2;
    cout<<"固件product_code:"<<buf<<endl;
    buf.clear();
    for(i = 0; i < 8;i++)
    {
        buf.push_back(contents[len+i*2]);
    }

    len = len+i*2;
    cout<<"固件版本:"<<buf<<endl;
    buf.clear();
    for(i = 0; i < 8;i++)
    {
        buf.push_back(contents[len+i*2]);
    }
    len = len+i*2;
    cout<<"log长度:"<<buf<<endl;

    stringstream stream(buf);
    stream>>loglen;
    cout<<loglen<<endl;
    cout<<"log :"<<endl;
    for(i = 0; i <loglen ;i++)
    {
       cout<<contents[len+i]; 
    }
    cout<<endl;
    len =len +i+16;

    data->buffer_size = contents.size()-len;
    


    if (data->buffer_size) {
        // allocate buffer and read file into buffer
        data->buffer = new uint8_t[data->buffer_size];
        contents.copy((char *) data->buffer, data->buffer_size,len);
    }
}


/****************************************************************************/
void CommandCode::showProdoctCode(
        MasterDevice &m,
        const SlaveList &slaves
        )
{
    SlaveList::const_iterator si;
    int i;

    for (si = slaves.begin(); si != slaves.end(); si++) {
        cout<<"Product_code:    0x"<<setw(8) << hex<<si->product_code << endl;
    }

}
/****************************************************************************/

/****************************************************************************/

bool CommandCode::slaveInList(
        const ec_ioctl_slave_t &slave,
        const SlaveList &slaves
        )
{
    SlaveList::const_iterator si;

    for (si = slaves.begin(); si != slaves.end(); si++) {
        if (si->position == slave.position) {
            return true;
        }
    }

    return false;
}

/*****************************************************************************/
